﻿using System;
using System.Configuration;
using System.Threading.Tasks;
using XrmLibrary.Adapters;
using XrmLibrary.ConfigFile;
using XrmLibrary.Configurations;
using XrmLibrary.Exceptions;
using XrmLibrary.Extensions;
using XrmLibrary.MessageStrings;
using System.ServiceModel.Description;
using XrmLibrary.Pooling;
using XrmLibrary.Pooling.Collections;

namespace XrmLibrary
{
    /// <summary>
    /// A threadsafe shared CRM service
    /// </summary>
    public sealed class Services
    {
        private const String XrmLibraryConfigurationName = "xrmLibrary";

        private static volatile Services _instance;
        private static readonly Object SyncRoot = new Object();
        private static readonly Object PoolSyncRoot = new Object();
        private static Boolean _isFinishedLoading = false;
        private readonly XrmLibraryConfigSection _xrmLibraryConfiguration;
        private readonly CrmConfigurationList _crmConfigurations;
        private readonly OrganizationServicePoolList _organizationServicePools;
        private Boolean _areOrganizationServicePoolsInstantiated = false;

        private Services()
        {
            _xrmLibraryConfiguration = LoadXrmLibraryConfiguration();
            ValidateXrmLibraryConfiguration();
            _crmConfigurations = LoadCrmConfigurations();
            _organizationServicePools = new OrganizationServicePoolList();

            _isFinishedLoading = true;
        }

        private static XrmLibraryConfigSection LoadXrmLibraryConfiguration()
        {
            return (XrmLibraryConfigSection)ConfigurationManager.GetSection(XrmLibraryConfigurationName);
        }

        private void ValidateXrmLibraryConfiguration()
        {
            if (_xrmLibraryConfiguration == null)
            {
                throw new XrmLibraryException(String.Format(ErrorStrings.SectionMissingFromConfigFile, XrmLibraryConfigurationName));
            }
        }

        private CrmConfigurationList LoadCrmConfigurations()
        {
            CrmConfigurationList crmConfigurations = new CrmConfigurationList();
            crmConfigurations.AddConfigurations(_xrmLibraryConfiguration);
            crmConfigurations.LoadAllCrmConfigurations();
            return crmConfigurations;
        }

        public static Services Instance
        {
            get
            {
                if (!_isFinishedLoading)
                {
                    lock (SyncRoot)
                    {
                        if (!_isFinishedLoading)
                        {
                            _instance = new Services();
                        }
                    }
                }
                return _instance;
            }
        }

        public void LoadCrm2011Configuration(CrmServiceConfigurationElement crmServiceConfig)
        {
            _crmConfigurations.AddCrm2011Configuration(crmServiceConfig);
            _crmConfigurations[crmServiceConfig.Name].Load();
        }

        public void UnloadCrmConfiguration(Int32 index)
        {
            _crmConfigurations.RemoveAt(index);
        }

        public void UnloadCrmConfiguration(String configurationName)
        {
            UnloadCrmConfiguration(GetIndex(configurationName));
        }

        public IOrganizationServiceAdapter GetOrganizationServiceFromPool()
        {
            if (!_areOrganizationServicePoolsInstantiated)
            {
                InstantiateOrganizationServicePools();
            }
            return _organizationServicePools[0].GetService();
        }

        public IOrganizationServiceAdapter GetOrganizationServiceFromPool(Int32 serviceIndex)
        {
            if (!_areOrganizationServicePoolsInstantiated)
            {
                InstantiateOrganizationServicePools();
            }
            return _organizationServicePools[serviceIndex].GetService();
        }

        public IOrganizationServiceAdapter GetOrganizationServiceFromPool(String configurationName)
        {
            if (!_areOrganizationServicePoolsInstantiated)
            {
                InstantiateOrganizationServicePools();
            }
            return _organizationServicePools[configurationName].GetService();
        }

        public void PutOrganizationServiceInPool(IOrganizationServiceAdapter service)
        {
            if (!_areOrganizationServicePoolsInstantiated)
            {
                InstantiateOrganizationServicePools();
            }
            _organizationServicePools[0].PutService(service);
        }

        public void PutOrganizationServiceInPool(Int32 serviceIndex, IOrganizationServiceAdapter service)
        {
            if (!_areOrganizationServicePoolsInstantiated)
            {
                InstantiateOrganizationServicePools();
            }
            _organizationServicePools[serviceIndex].PutService(service);
        }

        public void PutOrganizationServiceInPool(String configurationName, IOrganizationServiceAdapter service)
        {
            if (!_areOrganizationServicePoolsInstantiated)
            {
                InstantiateOrganizationServicePools();
            }
            _organizationServicePools[configurationName].PutService(service);
        }

        public void InstantiateOrganizationServicePools()
        {
            lock (PoolSyncRoot)
            {
                if (!_areOrganizationServicePoolsInstantiated)
                {
                    for (int i = 0; i < ServiceCount; i++)
                    {
                        _organizationServicePools.Add(OrganizationServicePool.Instantiate(i, _xrmLibraryConfiguration.ServicePoolSize));
                    }
                }
            }
            _areOrganizationServicePoolsInstantiated = true;
        }

        public Int32 ServicePoolCount
        {
            get { return _organizationServicePools[0].GetServiceCount(); }
        }

        public Int32 GetServicePoolCount(Int32 serviceIndex)
        {
            return _organizationServicePools[serviceIndex].GetServiceCount();
        }

        /// <summary>
        /// Gets the default organization service proxy.
        /// </summary>
        public IOrganizationServiceAdapter OrganizationService
        {
            get { return _crmConfigurations[0].CreateOrganizationServiceAdapter(); }
        }

        /// <summary>
        /// Gets the specified organization service proxy.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns>The specified organization service proxy.</returns>
        public IOrganizationServiceAdapter GetOrganizationService(Int32 index)
        {
            _crmConfigurations.ValidateCrmConfigurationExists(index);
            return _crmConfigurations[index].CreateOrganizationServiceAdapter();
        }

        public IOrganizationServiceAdapter GetOrganizationService(String configurationName)
        {
            _crmConfigurations.ValidateCrmConfigurationExists(configurationName);
            return _crmConfigurations[configurationName].CreateOrganizationServiceAdapter();
        }

        /// <summary>
        /// Gets the default discovery service proxy.
        /// </summary>
        public IDiscoveryServiceAdapter DiscoveryService
        {
            get { return _crmConfigurations[0].CreateDiscoveryServiceAdapter(); }
        }

        /// <summary>
        /// Gets the specified discovery service proxy.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns>The specified discovery service proxy.</returns>
        public IDiscoveryServiceAdapter GetDiscoveryService(Int32 index)
        {
            _crmConfigurations.ValidateCrmConfigurationExists(index);
            return _crmConfigurations[index].CreateDiscoveryServiceAdapter();
        }

        public IDiscoveryServiceAdapter GetDiscoveryService(String configurationName)
        {
            _crmConfigurations.ValidateCrmConfigurationExists(configurationName);
            return _crmConfigurations[configurationName].CreateDiscoveryServiceAdapter();
        }

        /// <summary>
        /// Gets a value indicating whether the default discovery service is configured.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if the default discovery service is configured; otherwise, <c>false</c>.
        /// </value>
        public Boolean IsCrmDiscoveryServiceConfigured
        {
            get { return _crmConfigurations[0].IsCrmDiscoveryServiceConfigured; }
        }

        /// <summary>
        /// Gets a value indicating whether the specified discovery service is configured.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <returns>true if the specified discovery service exists; otherwise, false.</returns>
        public Boolean GetIsCrmDiscoveryServiceConfigured(Int32 index)
        {
            _crmConfigurations.ValidateCrmConfigurationExists(index);
            return _crmConfigurations[index].IsCrmDiscoveryServiceConfigured;
        }

        public Boolean GetIsCrmDiscoveryServiceConfigured(String configurationName)
        {
            return _crmConfigurations[configurationName] != null;
        }

        public Int32 ServiceCount
        {
            get { return _crmConfigurations.Count; }
        }

        internal String GetConfigurationName(Int32 index)
        {
            return _crmConfigurations[index].GetConfigurationName();
        }

        internal Int32 GetIndex(String configurationName)
        {
            for(Int32 i = 0; i < _crmConfigurations.Count; i++)
            {
                if (_crmConfigurations[i].GetConfigurationName() == configurationName)
                {
                    return i;
                }
            }
            throw new XrmLibraryException("The configuration did not exist for specified configuration name.");
        }

        /// <summary>
        /// Reinitializes the CRM service using a new set of credentials.
        /// </summary>
        /// <param name="credentials">The credentials.</param>
        /// <param name="index">The index.</param>
        public void Reinitialize(ClientCredentials credentials, Int32 index = 0)
        {
            _crmConfigurations[index].Reinitialize(credentials);
        }

        /// <summary>
        /// Reinitializes the CRM service using a new user name and password
        /// </summary>
        /// <param name="userName">Name of the user.</param>
        /// <param name="password">The password.</param>
        /// <param name="index">The index.</param>
        public void Reinitialize(String userName, String password, Int32 index = 0)
        {
            ClientCredentials credentials = new ClientCredentials();
            credentials.UserName.UserName = userName;
            credentials.UserName.Password = password;
            Reinitialize(credentials);
        }
    }
}
